//
//  main.cpp
//  iphttpd
//
//  Created by Zhibiao Pan on 11-11-12.
//  Copyright 2011年 PanZhibiao. All rights reserved.
//
//  Build:
//    g++ -o iphttpd http_server.cc parser.cc iphttpd.cc -I/usr/local/include -I/usr/include -levent
//
//
#include "parser.h"
#include "http_server.h"

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#define IPHTTPD_SERVER_NAME       "IpHttpd/0.1"
#define IPHTTPD_PID_FILE          "/tmp/iphttpd.pid"
#define IPHTTPD_MAX_RESTART_CHILD 5           // max restart child process times

#define IPHTTPD_DEFAULT_HOST     "0.0.0.0"
#define IPHTTPD_DEFAULT_PORT     8080
#define IPHTTPD_DEFAULT_THREADS  4



/*************************   Function Define  *************************/
int lockfile(int fd);
int already_running(const char *lock_file);

void add_header_content_type(struct evhttp_request *req,
                             const char *type, const char *charset);
void http_cb(struct evhttp_request *req, void *arg);
void echo_server_status(struct evhttp_request *req);
void ip_output(struct evhttp_request *req);
void print_help();

/* 子进程信号处理 */
static void kill_signal_worker(const int sig) {
  exit(0);
}

/* 父进程信号处理 */
// TODO 杀死父进程后，子进程没有自动退出
static void kill_signal_master(const int sig) {
  /* 给进程组发送SIGTERM信号，结束子进程 */
  kill(0, SIGTERM);
  exit(0);
}

/*****************************   全局变量  *****************************/
time_t g_start_time;
long long g_accept_req_num;
iphttpd::CZParser *g_ipdb_hd;



/*****************************   Main  *****************************/
int main (int argc, char *argv[]) {
  if (argc <= 1) {
    print_help();
    exit(EXIT_FAILURE);
  }
  
  g_start_time = time(NULL);
  
  char iphttpd_qqwry_file[500] = {0};
  char iphttpd_listen_host[16] = IPHTTPD_DEFAULT_HOST;
  int  iphttpd_listen_port     = IPHTTPD_DEFAULT_PORT;
  int  iphttpd_threads         = 4;
  int  iphttpd_daemon          = 1;
  
  int optchr = 0;
  while ((optchr = getopt(argc, argv, "l:p:t:f:dh")) != -1) {
    switch (optchr) {
      case 'l':
        memcpy(iphttpd_listen_host, optarg, sizeof(iphttpd_listen_host)-1);
        break;
      case 'p':
        iphttpd_listen_port = atoi(optarg);
        break;
      case 't':
        iphttpd_threads = atoi(optarg);
        if (iphttpd_threads <= 0 || iphttpd_threads > 1024) {
          iphttpd_threads = IPHTTPD_DEFAULT_THREADS;
        }
        break;
      case 'f':
        memcpy(iphttpd_qqwry_file, optarg, sizeof(iphttpd_qqwry_file)-1);
        break;
      case 'd':
        iphttpd_daemon = atoi(optarg) == 1 ? 1 : 0;
      case 'h':
      default:
        print_help();
        exit(EXIT_FAILURE);
    }
  }
  
  // just check ip data file
  g_ipdb_hd = new iphttpd::CZParser();
  if (g_ipdb_hd->Init(iphttpd_qqwry_file) != 0) {
    printf("error when load ip data file\n");
    exit(EXIT_FAILURE);
  }
  free(g_ipdb_hd);
  g_ipdb_hd = NULL;
    
  // daemon
  if (1 == iphttpd_daemon) {
    pid_t pid;
    pid = fork();
    if (pid < 0) {
      exit(EXIT_FAILURE);
    } else if (pid > 0) {
      exit(EXIT_SUCCESS);
    }
  }
  if (already_running(IPHTTPD_PID_FILE)) {
    printf("error iphttpd already running\n");
    exit(EXIT_FAILURE);    
  }
  
  // fork子进程
  pid_t iphttpd_worker_pid_wait;
  pid_t iphttpd_worker_pid = fork();
  if (iphttpd_worker_pid < 0) {
    fprintf(stderr, "Error: %s:%d\n", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }
  
  /* ------------------- 父进程(守护进程) ------------------- */
  if (iphttpd_worker_pid > 0) {
    signal(SIGPIPE, SIG_IGN);
    signal(SIGINT,  kill_signal_master);
    signal(SIGKILL, kill_signal_master);
    signal(SIGQUIT, kill_signal_master);
    signal(SIGTERM, kill_signal_master);
    signal(SIGHUP,  kill_signal_master);
    signal(SIGSEGV, kill_signal_master);
    
    // TODO 修改进程名称，方便查看时识别父子进程
    
    // 等待，直至子进程退出，fork新的子进程
    int restart_times = 0;
    while (1) {
      iphttpd_worker_pid_wait = wait(NULL);
      if (iphttpd_worker_pid_wait < 0) { continue; }
      sleep(1);
      
      iphttpd_worker_pid = fork();
      if (++restart_times > IPHTTPD_MAX_RESTART_CHILD) {
        exit(EXIT_FAILURE);
      }
      if (iphttpd_worker_pid == 0) { break; }
    }
  }
  
  /* ------------------- 子进程(工作进程) ------------------- */
  signal(SIGPIPE, SIG_IGN);
  signal(SIGINT,  kill_signal_worker);
  signal(SIGKILL, kill_signal_worker);
  signal(SIGQUIT, kill_signal_worker);
  signal(SIGTERM, kill_signal_worker);
  signal(SIGHUP,  kill_signal_worker);
  signal(SIGSEGV, kill_signal_worker);
  
  g_ipdb_hd = new iphttpd::CZParser();
  if (g_ipdb_hd->Init(iphttpd_qqwry_file) != 0) {
    printf("error when load ip data file\n");
    return EXIT_FAILURE;
  }
  
  HTTPServer s;
  if (s.run(iphttpd_listen_host, iphttpd_listen_port, iphttpd_threads,
            http_cb) != 0) {
    fprintf(stderr, "error: bind() to %s:%d failed\n",
            iphttpd_listen_host, iphttpd_listen_port);
    sleep(1);
    exit(EXIT_FAILURE);
  }
  
  return 0;
} // /main



/*****************************   Functions  *****************************/
void print_help() {
  const static char *h = "%s - simple HTTP service for query ip location\n"
  "-l <ip_addr>  interface to listen on (default: INADDR_ANY, all addresses)\n"
  "-p <num>      TCP port number to listen on (default: %d)\n"
  "-t <num>      number of threads to use (default: %d)\n"
  "-d            run as a daemon\n"
  "-f <file>     IP data file (data format must be similar with 'QQwry.dat')\n"
  ;
  fprintf(stdout, h,
          IPHTTPD_SERVER_NAME,
          IPHTTPD_DEFAULT_PORT,
          IPHTTPD_DEFAULT_THREADS);
}

// add header: content-type
void add_header_content_type(struct evhttp_request *req,
                             const char *type, const char *charset) {
  char content_type[40] = {0};
  snprintf(content_type, sizeof(content_type), "%s; charset=%s", type, charset);
  evhttp_add_header(req->output_headers, "Content-Type", content_type);
}


// http callback function
void http_cb(struct evhttp_request *req, void *arg) {
  g_accept_req_num++;
  
  // Only Allow: HTTP GET
  if (EVHTTP_REQ_GET != req->type){
    evhttp_send_error(req, 405, "Method Not Allowed");
    return;
  }
  evhttp_add_header(req->output_headers, "Server", IPHTTPD_SERVER_NAME);
  evhttp_add_header(req->output_headers, "Connection", "close");
  add_header_content_type(req, "text/plain", "GB18030");
  
  if (strcmp(req->uri, "/_status") == 0) {  // output server status
    echo_server_status(req);
    return;
  }
  
  ip_output(req);
}


void ip_output(struct evhttp_request *req) {
  // 解析URL参数
  char *decode_uri = strdup((char *)evhttp_request_uri(req));
  struct evkeyvalq http_query;
  evhttp_parse_query(decode_uri, &http_query);
  free(decode_uri);
  
  const char *q = evhttp_find_header(&http_query, "ip");
  char *ip_str = NULL;
  if (q != NULL) { ip_str = strdup(q); }
  
  struct evbuffer *evbuf = evbuffer_new();
  if (evbuf == NULL)
    err(1, "failed to create response buffer");
  
  iphttpd::IPEntry ip_entry;
  memset(&ip_entry, '\0', sizeof(ip_entry));
  
  bool res = false;
  if (ip_str != NULL) {
    res = g_ipdb_hd->GetLocation(ip_str, (int)strlen(ip_str), ip_entry);
  }
  
 if(!res) {
    evbuffer_add_printf(evbuf, "error");
  }else{
//    char buf[64] = {0};
//    iphttpd::Ip2Str(buf, sizeof(buf) - 1, ip_entry.begin_ip);
//    evbuffer_add_printf(evbuf, "%s\t", buf);
    
//    memset(buf, '\0', sizeof(buf));
//    iphttpd::Ip2Str(buf, sizeof(buf) - 1, ip_entry.end_ip);
//    evbuffer_add_printf(evbuf, "%s\t", buf);
    
    evbuffer_add_printf(evbuf, "%s\t", (char *)ip_entry.country);
    evbuffer_add_printf(evbuf, "%s", (char *)ip_entry.area);
  }
  
  evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
  evbuffer_free(evbuf);
  
  evhttp_clear_headers(&http_query);
  free(ip_str);
}


// echo_server_status
void echo_server_status(struct evhttp_request *req) {
  // output buffer
  struct evbuffer *buf = evbuffer_new();
  if (buf == NULL)
    err(1, "failed to create response buffer");
  
  // g_start_time
  int diff_time = (int)(time(NULL) - g_start_time);
  const static char *status = "run time: %d days, %d hours, %d min\n"
  "total request times: %lld\n";
  evbuffer_add_printf(buf, status,
                      diff_time/86400, diff_time%86400/3600, diff_time%3600/60,
                      g_accept_req_num);
  evhttp_send_reply(req, HTTP_OK, "OK", buf);
  evbuffer_free(buf);
}


// lock file
int lockfile(int fd) {
  struct flock fl;
  fl.l_type   = F_WRLCK;
  fl.l_start  = 0;
  fl.l_whence = SEEK_SET;
  fl.l_len    = 0;
  return fcntl(fd, F_SETLK, &fl);
}

// check if already running
// 用于确定进程唯一运行, 已经在运行则返回 1
int already_running(const char *lock_file) {
  int       fd;
  char buf[16];
  
  fd = open(lock_file, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
  if (fd < 0) {
    syslog(LOG_ERR, "cant't open %s: %s", lock_file, strerror(errno));
    exit(1);
  }
  if (lockfile(fd) < 0) {
    if (errno == EACCES || errno == EAGAIN) {
      close(fd);
      return 1;
    }
    syslog(LOG_ERR, "cant't lock %s: %s", lock_file, strerror(errno));
    exit(1);
  }
  ftruncate(fd, 0);
  sprintf(buf, "%ld", (long)getpid());
  write(fd, buf, strlen(buf)+1);
  return 0;
}
